#include <algorithm/analytical_model/algorithm/Area.h>
#include <algorithm/analytical_model/algorithm/Center.h>
#include <algorithm/analytical_model/algorithm/Distance.h>
#include <algorithm/analytical_model/algorithm/FocusCenter.h>
#include <algorithm/analytical_model/algorithm/Normal.h>
#include <algorithm/analytical_model/algorithm/cpu/VectorConvert.h>
#include <algorithm/analytical_model/algorithm/cpu/receiver/CylinderReceiver.h>
#include <algorithm/shared/data/topology/geometry/Cylinder3D.h>
#include <algorithm/shared/data/topology/geometry/Rectangle3D.h>
#include <utils/HasValue.h>
#include <utils/ParaUtils.h>
#include <utils/config.h>

using namespace solar;
using namespace solar::cuda::inter_op;

template <> auto solar::area(Rectangle3D* rect, bool lazy) -> double
{
    const auto current_area = SOLAR_GET_VALUE(Rectangle3D, area_, *rect);
    if (::solar::hasValue(current_area) && lazy)
    {
        return current_area;
    }
    const auto size = SOLAR_GET_VALUE(Rectangle3D, size_, *rect);
    if (!::solar::hasValue(size)) [[unlikely]]
    {
        const auto vertices = SOLAR_GET_VALUE(Rectangle3D, vertices_, *rect);
        std::array<double, 2> size_data{}; // {width, height}
        size_data[0] = (vertices[3] - vertices[0]).length();
        size_data[1] = (vertices[1] - vertices[0]).length();
        auto vec = size.get_vec();
        vec.store_partial(2, size_data.data());
        SOLAR_SET_VALUE(Rectangle3D, size_, vec, *rect)
    }
    const auto size_data = size.data();
    const auto area = size_data[0] * size_data[2];
    SOLAR_SET_VALUE(Rectangle3D, area_, area, *rect)

    return area;
}

template <> auto solar::normal(Rectangle3D* rect, bool lazy) -> Vec3
{
    const auto current_normal = SOLAR_GET_VALUE(Rectangle3D, normal_, *rect);
    if (solar::hasValue(current_normal) && lazy)
    {
        return current_normal;
    }
    const auto vertices = SOLAR_GET_VALUE(Rectangle3D, vertices_, *rect);
    const auto ret = solar::cross(vertices[3] - vertices[0], vertices[1] - vertices[0]);
    SOLAR_SET_VALUE(Rectangle3D, normal_, ret, *rect)
    return ret.get_unit_vector();
}

template <> auto solar::center(Rectangle3D* rect, bool lazy) -> Vec3
{
    const auto current_center = SOLAR_GET_VALUE(Rectangle3D, center_, *rect);
    if (solar::hasValue(current_center) && lazy)
    {
        return current_center;
    }
    const auto vertices = SOLAR_GET_VALUE(Rectangle3D, vertices_, *rect);
    auto ret = Vec3(0, 0, 0);
    for (const auto& vec : vertices)
    {
        ret += vec;
    }

    return ret / vertices.size();
}

template <> auto solar::focusCenter(Rectangle3D* /*unused*/, Rectangle3D* dst) -> Vec3
{
    return solar::center(dst);
}

template <> auto solar::focusCenter(Rectangle3D* src, Cylinder3D* dst) -> Vec3
{
    const float radius = SOLAR_GET_VALUE(Cylinder3D, radius_, *dst);
    const float height = SOLAR_GET_VALUE(Cylinder3D, height_, *dst);
    const auto center = SOLAR_GET_VALUE(Cylinder3D, center_, *dst);
    const auto src_center = solar::center(src);
    const auto cylinder_intersection = cpu::toVec3(
        cpu::getFocusCenter(cpu::toFloat3(src_center), {radius, height, 0}, cpu::toFloat3(center)));
    return cylinder_intersection;
}

template <> auto solar::distance(Rectangle3D* src, Rectangle3D* dst) -> double
{
    return (solar::center(dst) - solar::center(src)).length();
}

template <> auto solar::distance(Rectangle3D* src, Cylinder3D* dst) -> double
{
    const auto cylinder_intersection = solar::focusCenter(src, dst);
    return (cylinder_intersection - solar::center(src)).length();
}
